package com.magnificent.graph.view;

import com.magnificent.graph.model.mxGeometry;
import com.magnificent.graph.model.mxIGraphModel;
import com.magnificent.graph.util.mxEvent;
import com.magnificent.graph.util.mxEventObject;
import com.magnificent.graph.util.mxEventSource;
import com.magnificent.graph.util.mxRectangle;

public class mxSwimlaneManager extends mxEventSource {

    /**
     * Defines the type of the source or target terminal. The type is a string
     * passed to Cell.is to check if the rule applies to a cell.
     */
    protected mxGraph graph;

    /**
     * Optional string that specifies the value of the attribute to be passed
     * to Cell.is to check if the rule applies to a cell.
     */
    protected boolean enabled;

    /**
     * Optional string that specifies the attributename to be passed to
     * Cell.is to check if the rule applies to a cell.
     */
    protected boolean horizontal;

    /**
     * Optional string that specifies the attributename to be passed to
     * Cell.is to check if the rule applies to a cell.
     */
    protected boolean siblings;

    /**
     * Optional string that specifies the attributename to be passed to
     * Cell.is to check if the rule applies to a cell.
     */
    protected boolean bubbling;

    /**
     *
     */
    protected mxIEventListener addHandler = new mxIEventListener() {
        public void invoke(Object source, mxEventObject evt) {
            if (isEnabled()) {
                cellsAdded((Object[]) evt.getProperty("cells"));
            }
        }
    };

    /**
     *
     */
    protected mxIEventListener resizeHandler = new mxIEventListener() {
        public void invoke(Object source, mxEventObject evt) {
            if (isEnabled()) {
                cellsResized((Object[]) evt.getProperty("cells"));
            }
        }
    };

    /**
     *
     */
    public mxSwimlaneManager(mxGraph graph) {
        setGraph(graph);
    }

    /**
     *
     */
    public boolean isSwimlaneIgnored(Object swimlane) {
        return !getGraph().isSwimlane(swimlane);
    }

    /**
     * @return the enabled
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * @param value the enabled to set
     */
    public void setEnabled(boolean value) {
        enabled = value;
    }

    /**
     * @return the bubbling
     */
    public boolean isHorizontal() {
        return horizontal;
    }

    /**
     * @param value the bubbling to set
     */
    public void setHorizontal(boolean value) {
        horizontal = value;
    }

    /**
     * @return the bubbling
     */
    public boolean isSiblings() {
        return siblings;
    }

    /**
     * @param value the bubbling to set
     */
    public void setSiblings(boolean value) {
        siblings = value;
    }

    /**
     * @return the bubbling
     */
    public boolean isBubbling() {
        return bubbling;
    }

    /**
     * @param value the bubbling to set
     */
    public void setBubbling(boolean value) {
        bubbling = value;
    }

    /**
     * @return the graph
     */
    public mxGraph getGraph() {
        return graph;
    }

    /**
     * @param graph the graph to set
     */
    public void setGraph(mxGraph graph) {
        if (this.graph != null) {
            this.graph.removeListener(addHandler);
            this.graph.removeListener(resizeHandler);
        }

        this.graph = graph;

        if (this.graph != null) {
            this.graph.addListener(mxEvent.ADD_CELLS, addHandler);
            this.graph.addListener(mxEvent.CELLS_RESIZED, resizeHandler);
        }
    }

    /**
     *
     */
    protected void cellsAdded(Object[] cells) {
        if (cells != null) {
            mxIGraphModel model = getGraph().getModel();

            model.beginUpdate();
            try {
                for (int i = 0; i < cells.length; i++) {
                    if (!isSwimlaneIgnored(cells[i])) {
                        swimlaneAdded(cells[i]);
                    }
                }
            }
            finally {
                model.endUpdate();
            }
        }
    }

    /**
     *
     */
    protected void swimlaneAdded(Object swimlane) {
        mxIGraphModel model = getGraph().getModel();

        // Tries to find existing swimlane for dimensions
        // TODO: Use parent geometry - header if inside
        // parent swimlane
        mxGeometry geo = null;
        Object parent = model.getParent(swimlane);
        int childCount = model.getChildCount(parent);

        for (int i = 0; i < childCount; i++) {
            Object child = model.getChildAt(parent, i);

            if (child != swimlane && !isSwimlaneIgnored(child)) {
                geo = model.getGeometry(child);
                break;
            }
        }

        // Applies dimension to new child
        if (geo != null) {
            model.beginUpdate();
            try {
                resizeSwimlane(swimlane, geo.getWidth(), geo.getHeight());
            }
            finally {
                model.endUpdate();
            }
        }
    }

    /**
     *
     */
    protected void cellsResized(Object[] cells) {
        if (cells != null) {
            mxIGraphModel model = getGraph().getModel();

            model.beginUpdate();
            try {
                for (int i = 0; i < cells.length; i++) {
                    if (!isSwimlaneIgnored(cells[i])) {
                        swimlaneResized(cells[i]);
                    }
                }
            }
            finally {
                model.endUpdate();
            }
        }
    }

    /**
     *
     */
    protected void swimlaneResized(Object swimlane) {
        mxIGraphModel model = getGraph().getModel();
        mxGeometry geo = model.getGeometry(swimlane);

        if (geo != null) {
            double w = geo.getWidth();
            double h = geo.getHeight();

            model.beginUpdate();
            try {
                Object parent = model.getParent(swimlane);

                if (isSiblings()) {
                    int childCount = model.getChildCount(parent);

                    for (int i = 0; i < childCount; i++) {
                        Object child = model.getChildAt(parent, i);

                        if (child != swimlane && !isSwimlaneIgnored(child)) {
                            resizeSwimlane(child, w, h);
                        }
                    }
                }

                if (isBubbling() && !isSwimlaneIgnored(parent)) {
                    resizeParent(parent, w, h);
                    swimlaneResized(parent);
                }
            }
            finally {
                model.endUpdate();
            }
        }
    }

    /**
     *
     */
    protected void resizeSwimlane(Object swimlane, double w, double h) {
        mxIGraphModel model = getGraph().getModel();
        mxGeometry geo = model.getGeometry(swimlane);

        if (geo != null) {
            geo = (mxGeometry) geo.clone();

            if (isHorizontal()) {
                geo.setWidth(w);
            } else {
                geo.setHeight(h);
            }

            model.setGeometry(swimlane, geo);
        }
    }

    /**
     *
     */
    protected void resizeParent(Object parent, double w, double h) {
        mxIGraphModel model = getGraph().getModel();
        mxGeometry geo = model.getGeometry(parent);

        if (geo != null) {
            geo = (mxGeometry) geo.clone();
            mxRectangle size = graph.getStartSize(parent);

            if (isHorizontal()) {
                geo.setWidth(w + size.getWidth());
            } else {
                geo.setHeight(h + size.getHeight());
            }

            model.setGeometry(parent, geo);
        }
    }

    /**
     *
     */
    public void destroy() {
        setGraph(null);
    }

}

  /* converted to utf8 */